#
# "@(#) $Id: CDROM.py,v 1.1.1.1 2006-07-27 03:16:08 gioshe Exp $"
#
# This work is released under the GNU GPL, version 2 or later.
#
from Utils.kdeemul import *
import os, fcntl, array, sys, time, re, Utils.backtick
from Burner import *

_lastRecordingCheckTime = 0
_lastRecordingCheckValue = False

#
# the singleton instance of the CDROM drive
#

class CDROM(QObject):
	def __init__(self,info):
		QObject.__init__(self)
		print "creating drive with",info
		self.info = info
		self.driver = '/dev/'+self.info['drive name']
		self.busDevice = self.info['Bus Device']
		self._cd = True # check for availability of CD
		self._hasCD = False
		self._firstCheck = True
		self._numTracks = 0
		self._burnerInfo = None
		#self.startTimer()

	#
	# set up timer to check whether or not a CD has been inserted or ejected
	#
	def startTimer(self):
		self.timer = QTimer()
		self.timer.start(4000)
		self.connect(self.timer,SIGNAL("timeout()"),self.checkStatus)

	def stopTimer(self):
		self.timer.stop()

	#
	# check the status of the drive - if the state changes, generate a signal
	#
	def checkStatus(self):
		if self._cd:
			hasCD = self.checkForMedia()
			if hasCD!=self._hasCD:
				print "media changed"
				if hasCD:
					self.timer.stop()
					self.emit(PYSIGNAL("cdInserted"),(self,self._firstCheck))
					#print "inserted"
					self.timer.start(4000)
				else:
					self.emit(PYSIGNAL("cdEjected"),(self,self._firstCheck))
					#print "ejected"
				self._hasCD = hasCD
		self._firstCheck = False

	def checkForMedia(self):
		#print "checking CD",self.driver
		if self.isRecording():
			return False
		try:
			isValidCD = False
			#print "opening driver"
			fd = os.open(self.driver,os.O_RDWR | os.O_NONBLOCK)
			#print "checking tray/media"
			s = fcntl.ioctl(fd,0x5326) # check if tray closed, media present
			if s==4:
				#print "checking media type"
				s = fcntl.ioctl(fd,0x5327) # check media type
				isValidCD = (s==100 or s==105) # valid if audio or mixed
				if isValidCD:
					self._numTracks = self.getNumTracks(fd)
			#print "closing",self._numTracks,"\n"
			os.close(fd)
			return isValidCD
		except:
			#print "check exception",sys.exc_info()[0]
			try: os.close(fd)
			except: pass
			return False
	
	def getNumTracks(self,fd):
		# the new way of doing ioctl with results
		a = array.array('b',[0,0])
		s = fcntl.ioctl(fd,0x5305,a,1)
		if s==0: return a[1]

	def getTocEntry(self,fd,index):
		if LsongsPlatform=='Lindows':
			s = fcntl.ioctl(fd,0x5306,'        ')
		else:
			a = array.array('b',[0,0,0,0,0,0,0,0])
			s = fcntl.ioctl(fd,0x5306,a,1)
			if s==0: pass

	def numTracks(self):
		return self._numTracks

	def hasCD(self):
		return self._hasCD

	def cd(self):
		return self._cd

	#
	# attempt to eject the currently mounted CD
	#
	def eject(self):
		if self._cd:
			try:
				self.ejectMedia()
			except:
				KMessageBox.error(None,i18n("Cannot eject CD from CDROM drive"),i18n("Cannot Eject CD"))
				return False
		return True


	def ejectMedia(self):
		try:
			fd = os.open(self.driver,os.O_RDWR | os.O_NONBLOCK)
			fcntl.ioctl(fd,0x5309)
			os.close(fd)
		except: pass

	#
	# see if anyone is burning a CD - strategy is to
	# look through the command lines for all the running processes
	# and see if any of them are cdrecord or cdrao
	#
	# this isn't perfect = someone could be using something else
	# but currently there's no other universal method of detecting
	# an ongoing burn from userspace
	#
	# we throttle this so that it only happens every couple of seconds
	#
	def isRecording(self):
		global _lastRecordingCheckTime,_lastRecordingCheckValue
		
		now = time.time()
		if now>_lastRecordingCheckTime+2:
			def filter(args,dirname,files):
				for file in files:
					try:
						if str(int(file))==file: # found a process
							fullpath = os.path.normpath(os.path.join(dirname,file,"cmdline"))
							cmdline = open(fullpath,"rb").read()
							try:
								if cmdline.index("cdrecord")>=0: args.append(1)
							except: pass
							try:
								if cmdline.index("cdrdao")>=0: args.append(1)
							except: pass
					except: pass
				files[:] = []
			#print "scanning processes"
			result = []
			os.path.walk('/proc',filter,result)
			_lastRecordingCheckValue = len(result)>0
			_lastRecordingCheckTime = now
		return _lastRecordingCheckValue
	
	def canBurn(self):
		return self.info.get("Can write CD-R",False)

	def burnerInfo(self):
		if self._burnerInfo == None and self.canBurn():
			try:
				vendorPattern = re.compile("Vendor_info\s*?: '(.+?)\s*?'")
				modelPattern = re.compile("Identifikation\s*?: '(.+?)\s*?'")
				speedPattern = re.compile('^\s+?Maximum write speed:.+?\(CD\s+?(\d+)x')
				capPattern = re.compile('^\s+?Does write (.+?) media')
				address = self.info["Bus Device"]
				result = backtick.backtick("cdrecord dev=%s -prcap" % address)
				supports = []
				for line in result.split("\n"):
					mo = vendorPattern.match(line)
					if mo: vendor = mo.group(1)
					mo = modelPattern.match(line)
					if mo: model = mo.group(1)
					mo = capPattern.match(line)
					if mo: supports.append(mo.group(1))
					mo = speedPattern.match(line)
					if mo: speed = int(mo.group(1))
				self._burnerInfo = {"Address":address,"Manufacturer":vendor,"Model":model,"MaxSpeed":speed,"Supports":supports}
			except: pass
		return self._burnerInfo
			

class CDROMS(QObject):
	def __init__(self):
		QObject.__init__(self)
		self.scanForDrives()
	
	def scanForDrives(self):
		#print "CDROMS.scanForDrives: scanning for CD drives"
		self._drives = []
		try:
			fd = open("/proc/sys/dev/cdrom/info","r")
			data = fd.read()
			lines = data.split('\n')
			results = []
			for line in lines:
				fields = line.split(":")
				tag = fields[0]
				if tag.find(',')<0:
					try:
						fields = fields[1].split()
						while 1:
							try: fields.remove('')
							except: break
						while len(results)<len(fields):
							results.append({})
						for r,field in map(None,results,fields):
							r[tag] = field
					except: pass
			scsiPattern = re.compile('^/dev/scsi/host(\d*)/bus(\d*)/target(\d*)/lun(\d*)/cd')
			idePattern = re.compile('^/dev/ide/host(\d*)/bus(\d*)/target(\d*)/lun(\d*)/cd')
			for result in results:
				busDevice = "/dev/"+result['drive name']
				realpath = os.path.realpath(busDevice)
				#print "real path is ",realpath
				mo = scsiPattern.match(realpath) # check for SCSI emulation
				if mo:
					busDevice = "%s,%s,%s" % (mo.group(2),mo.group(3),mo.group(4))
				mo = idePattern.match(realpath)
				if mo:
					#busDevice = "ATA:%s,%s,%s" %  (mo.group(2),mo.group(3),mo.group(4))
					drive = ord(result['drive name'][2])-ord('a')
					busDevice = "ATA:%d,%d,0" % (drive/2 ,drive % 2)
				result['Bus Device'] = busDevice
				#print "burner device",result
				cdrom = CDROM(result)
				self._drives.append(cdrom)
		except: pass
		#print "CDROMS.scanForDrives: done scanning for CD drives"
	
	def startTimer(self):
		#print "starting CDROM media scanner"
		for drive in self._drives:
			drive.startTimer()

	def stopTimer(self):
		#print "stopping CDROM media scanner"
		for drive in self._drives:
			drive.stopTimer()
	
	def drives(self):
		return self._drives
	
	def hasCD(self):
		for drive in self._drives:
			if drive.hasCD(): return True
		return False

	def burnerInfo(self):
		result = []
		for drive in self._drives:
			if drive.canBurn():
				info = drive.burnerInfo()
				if info:
					result.append(info)
		return result
	
	def canBurn(self):
		for drive in self._drives:
			if drive.canBurn():
				return True
		return False

	def static_singleton():
		global _CDROMSSingleton
		if _CDROMSSingleton==None:
			_CDROMSSingleton = CDROMS()
		return _CDROMSSingleton
	singleton = staticmethod(static_singleton)

_CDROMSSingleton = None
